(*****************************************************************************
 * Delphi Plug-in Framework (DPF)
 * Copyright (C) 2006-2007 Tao Yuan
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the
 * License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.                                                               }
 *
 * The Initial Developer of the Original Code is Tao Yuan. Portions created by
 * Tao Yuan are Copyright (C) Tao Yuan (taoy.cn att gmail dott com)
 * All rights reserved.
 *****************************************************************************)
 
{$INCLUDE jedi.inc}

unit RDPluginRegistryImpl;

interface

uses
  Classes,
  Rcl,
  RDIdentity, RDIdentityImpl, RdClasses, RdIntfs,
  RDPluginRegistry, RDExceptions, RDModel, RDVersion,
  RDConsts, RDManifestParser,
  RDIntegrityCheckReport, RDIntegrityChecker, RDPathResolver;

const
  PACKAGE_NAME = 'dpf.plugin.registry.xml'; //$NON-NLS-1$
  UNIQUE_SEPARATOR = '@';

type
  TPluginFragmentImpl = class;


  TManifestInfoImpl = class;
  TReferenceImpl = class(TRdLogPersistent, IReference)
  private
    doc: IDocumentation;
    modelRef: TModelDocumentationReference;
  protected
    function getCaption: String; stdcall;
    function getDeclaringIdentity: IIdentity; stdcall;
    function getRef: String; stdcall;
  public
    constructor Create(aDoc: IDocumentation; aModel:
        TModelDocumentationReference);
  end;

  TDocumentationImpl = class(TRdLogPersistent, IDocumentation)
  private
    identity: IIdentity;
    model: TModelDocumentation;
    references: IList;
  protected
    destructor Destroy; override;
    (**
     * @return plug-in element caption or empty string
     *)
    function getCaption: string; stdcall;
    {**
     * @return element, for which this documentation is provided
     *}
    function getDeclaringIdentity: IIdentity; stdcall;
    (**
     * @return collection of {@link Reference references} in this documentation
     *)
    function getReferences: IIntfCollection; stdcall;
    (**
     * @return main documentation text or empty string
     *)
    function getText: string; stdcall;
  public
    constructor Create(anIdentity: IIdentity; aModel: TModelDocumentation);
  end;

  TPluginDescriptorImpl = class(TIdentityImpl, IPluginDescriptor)
  private
    attributes: IList;
    doc: IDocumentation;
    extensionPoints: IStrMap;
    extensions: IStrMap;
    fragments: IList;
    libraries: IStrMap;
    location: string;
    model: TModelPluginDescriptor;
    pluginPrerequisites: IStrMap;
    registry: IPluginRegistry;
    procedure processAttributes(fragment: TPluginFragmentImpl; modelManifest:
        TModelPluginManifest);
    procedure processExtensionPoints(fragment: TPluginFragmentImpl; modelManifest:
        TModelPluginManifest);
    procedure processExtensions(fragment: TPluginFragmentImpl; modelManifest:
        TModelPluginManifest);
    procedure processLibraries(fragment: TPluginFragmentImpl; modelManifest:
        TModelPluginManifest);
    procedure processPrerequisites(fragment: TPluginFragmentImpl; modelManifest:
        TModelPluginManifest);
  protected
    function getExtension(id: string): IExtension; stdcall;
    function getExtensionPoint(id: string): IExtensionPoint; stdcall;
    (**
     * Returns collection of all extension points defined in manifest.
     * @return collection of {@link ExtensionPoint} objects
     *)
    function getExtensionPoints: IIntfCollection; stdcall;
    (**
     * Returns collection of all extensions defined in manifest.
     * @return collection of {@link Extension} objects
     *)
    function getExtensions: IIntfCollection;stdcall;
    function isEqualTo(idt: IIdentity): Boolean; override;
    function getAttribute(id: String): IPluginAttribute; stdcall;
    function getAttributes: IIntfCollection; overload; stdcall;
    function getAttributes(id: String): IIntfCollection; overload; stdcall;
    function getDocsPath: String; stdcall;
    function getDocumentation: IDocumentation; stdcall;
    function getFragments: IIntfCollection; stdcall;
    function getLibraries: IIntfCollection; stdcall;
    function getLibrary(id: string): ILibrary; stdcall;
    function getLocation: string; stdcall;
    function getPath: string; stdcall;
    function getPluginClassName: String; stdcall;
    function getPrerequisite(id: string): IPluginPrerequisite; stdcall;
    function getPrerequisites: IIntfCollection; stdcall;
    function getRegistry: IPluginRegistry; stdcall;
    function getUniqueId: String; stdcall;
    function getVendor: String; stdcall;
    function getVersion: TVersion; stdcall;
    function toString: string; override;
  public
    constructor Create(aRegistry: IPluginRegistry; aModel: TModelPluginDescriptor;
        aLocation: string);
    destructor Destroy; override;
    procedure registerFragment(fragment: TPluginFragmentImpl); stdcall;
    procedure unregisterFragment(fragment: TPluginFragmentImpl); stdcall;
  end;

  TPluginFragmentImpl = class(TIdentityImpl, IPluginFragment)
  private
    doc: IDocumentation;
    location: string;
    model: TModelPluginFragment;
    registry: IPluginRegistry;
  protected
    destructor Destroy; override;
    function getDocsPath: String; stdcall;
    function getDocumentation: IDocumentation; stdcall;
    function getLocation: string; stdcall;
    function getModel: TModelPluginFragment; stdcall;
    function getPluginId: String; stdcall;
    function getPluginVersion: TVersion; stdcall;
    function getRegistry: IPluginRegistry; stdcall;
    function getUniqueId: String; stdcall;
    function getVendor: String; stdcall;
    function getVersion: TVersion; stdcall;
    function matches(descr: IPluginDescriptor): Boolean; stdcall;
    function toString: string; override;
  public
    constructor Create(aRegistry: IPluginRegistry; aModel: TModelPluginFragment;
        aLocation: string);
  end;

  TPluginElementImpl = class(TIdentityImpl, IPluginElement)
  private
    descriptor: IPluginDescriptor;
    doc: TDocumentationImpl;
    fragment: IPluginFragment;
  protected
    destructor Destroy; override;
    function getDeclaringPluginDescriptor: IPluginDescriptor; stdcall;
    function getDeclaringPluginFragment: IPluginFragment; stdcall;
    function getDocsPath: String; stdcall;
    function getDocumentation: IDocumentation; stdcall;
    function isEqualTo(idt: IIdentity): Boolean; override;
    function getUniqueId: string; stdcall;
  public
    constructor Create(descr: IPluginDescriptor; aFragment: IPluginFragment; id:
        String; modelDoc: TModelDocumentation);
  end;

  TPluginAttributeImpl = class(TPluginElementImpl, IPluginAttribute)
  private
    superAttribute:TPluginAttributeImpl;
    model:TModelAttribute;
    subAttributes:Rcl.IList;
  protected
    (**
     * @param id ID of sub-attribute to look for
     * @return sub-attribute with given ID
     *)
    function getSubAttribute(id: String): IPluginAttribute; stdcall;
    (**
     * @return collection of all sub-attributes of this attribute
     *)
    function getSubAttributes: IIntfCollection; overload; stdcall;
    (**
     * @param id ID of sub-attribute to look for
     * @return collection of all sub-attributes with given ID
     *)
    function getSubAttributes(id: String): IIntfCollection; overload; stdcall;
    (**
     * @return attribute, of which this one is child or <code>null</code> if
     *         this is top level attribute
     *)
    function getSuperAttribute: IPluginAttribute; stdcall;
    (**
     * @return attribute value as it is specified in manifest
     *)
    function getValue: string; stdcall;
    function isEqualTo(idt: IIdentity): Boolean;
  public
    constructor Create(descr: TPluginDescriptorImpl; aFragment:
        TPluginFragmentImpl; aModel: TModelAttribute; aSuperAttribute:
        TPluginAttributeImpl );
  end;

  TPluginPrerequisiteImpl = class(TRdLogPersistent, IPluginPrerequisite)
  private
    descriptor: TPluginDescriptorImpl;
    fragment: TPluginFragmentImpl;
    model: TModelPrerequisite;
    doc: TDocumentationImpl;
  protected
    class function matches(source, target: TVersion; match: string): Boolean;
        overload;
    destructor Destroy; override;
      (**
       * Returns plug-in descriptor, this element belongs to. This method
       * should never return <code>null</code>.
       * @return plug-in descriptor, this element belongs to
       *)
    function getDeclaringPluginDescriptor: IPluginDescriptor; stdcall;
      (**
       * Returns descriptor of plug-in fragment that contributes this element.
       * This method may return <code>null</code>, if element is contributed by
       * plug-in directly.
       * @return descriptor of plug-in fragment that contributes this element
       *)
    function getDeclaringPluginFragment: IPluginFragment; stdcall;
    function getDocsPath: String; stdcall;
    function getDocumentation: IDocumentation; stdcall;
    (**
     * @return ID of plug-in manifest element
     *)
    function getId: string; stdcall;
    (**
     * @return the match rule as it specified in manifest
     *)
    function getMatch: String; stdcall;
    (**
     * @return ID of plug-in, this plug-in depends on
     *)
    function getPluginId: string; stdcall;
    (**
     * @return desired plug-in version identifier or <code>null</code>
     *         if not specified
     *)
    function getPluginVersion: TVersion; stdcall;
    function getUniqueId: string; stdcall;
    (**
     * @return <code>true</code> if this prerequisite is propagated
     *         on depending plug-ins
     *)
    function isExported: boolean; stdcall;
    (**
     * @return <code>true</code> if this prerequisite is not required
     *)
    function isOptional: boolean; stdcall;
    (**
     * @return <code>true</code> if this prerequisite is fulfilled
     *)
    function matches: boolean; overload; stdcall;
  public
    constructor Create(descr: TPluginDescriptorImpl; aFragment:
        TPluginFragmentImpl; aModel: TModelPrerequisite); overload;
  end;

  ///<metaclass>AssociationClass</metaclass>
  TRegistryChangeDataImpl = class(TRdLogPersistent, IRegistryChangeData)
  private
    addedPluginsSet,
    removedPluginsSet,
    modifiedPluginsSet: IStrSet;
    addedExtensionsMap,
    removedExtensionsMap,
    modifiedExtensionsMap: IStrStrMap;
    function getMapKeySet(aMap: IStrStrMap; aValue: string): IStrSet;
    procedure reset;

  protected
    procedure afterEventFire;
    procedure beforeEventFire;
    procedure dump;
    destructor Destroy; override;
    (**
     * @return collection of unique ID's of newly connected extensions
     *)
    function addedExtensions: IStrSet; overload; stdcall;
    (**
     * @param extensionPointUid unique ID of extension point to filter
     *                          result
     * @return collection of unique ID's of newly connected extensions
     *)
    function addedExtensions(extensionPointUid: String): IStrSet; overload; stdcall;
    (**
     * @return collection of ID's of newly added plug-ins
     *)
    function addedPlugins: IStrSet; stdcall;
    (**
     * @return collection of unique ID's of modified extensions
     *)
    function modifiedExtensions: IStrSet; overload; stdcall;
    (**
     * @param extensionPointUid unique ID of extension point to filter
     *                          result
     * @return collection of unique ID's of modified extensions
     *)
    function modifiedExtensions(extensionPointUid: String): IStrSet; overload;
        stdcall;
    (**
     * @return collection of ID's of changed plug-ins
     *)
    function modifiedPlugins: IStrSet; stdcall;
    procedure putAddedExtension(extensionUid, extensionPointUid: string);
    procedure putModifiedExtension(extensionUid, extensionPointUid: string);
    procedure putRemovedExtension(extensionUid, extensionPointUid: string);
    (**
     * @return collection of unique ID's of disconnected extensions
     *)
    function removedExtensions: IStrSet; overload; stdcall;
    (**
     * @param extensionPointUid unique ID of extension point to filter
     *                          result
     * @return collection of unique ID's of disconnected extensions
     *)
    function removedExtensions(extensionPointUid: String): IStrSet; overload;
        stdcall;
    (**
     * @return collection of ID's of removed plug-ins
     *)
    function removedPlugins: IStrSet; stdcall;
  public
    constructor Create;
  end;

  TPluginRegistryImpl = class(TRdLogPersistent, IPluginRegistry)
  private
    {$IFDEF DELPHI9_UP}
    const PACKAGE_NAME = 'TPluginRegistryImpl';
    {$ELSE}
    class function PACKAGE_NAME: string;
    {$ENDIF}
  private
    registrationReport: IList; // <ReportItemImpl>
    registeredPlugins: IStrMap;
    registeredFragments: IStrMap; // <fragmentId, PluginFragmentImpl>
    listeners: IIntfList; // <EventListener>
    manifestParser: TRdManifestParser;
    stopOnError: Boolean; // = false;
    isValidating: Boolean; // = true;
    procedure checkVersions(plugins: IStrMap);
    procedure dump;
    function registerFragment(url: string; model: TModelPluginFragment;
        registryChangeData: TRegistryChangeDataImpl): IPluginFragment;
    function registerPlugin(url: string; model: TModelPluginDescriptor;
        registryChangeData: TRegistryChangeDataImpl): IPluginDescriptor;
    procedure unregisterFragment(fragment: TPluginFragmentImpl);
    procedure unregisterPlugin(descr: TPluginDescriptorImpl);
  protected
    destructor Destroy; override;
    (**
     * Looks for extension point.
     * @param uniqueId extension point unique ID
     * @return plug-in extension point
     * @see #getExtensionPoint(String, String)
     *)
    function getExtensionPoint(uniqueId: String): IExtensionPoint; overload; stdcall;
    (**
     * Looks for extension point. This method have throw an
     * {@link IllegalArgumentException} if requested extension point
     * can't be found or is in invalid state.
     * @param pluginId plug-in ID
     * @param pointId extension point ID
     * @return plug-in extension point
     * @see ExtensionPoint#isValid()
     *)
    function getExtensionPoint(pluginId, pointId: string): IExtensionPoint;
        overload;stdcall;
    (**
     * Checks if extension point exists and is in valid state.
     * @param uniqueId extension point unique ID
     * @return <code>true</code> if extension point exists and valid
     * @see #isExtensionPointAvailable(String, String)
     *)
    function isExtensionPointAvailable(uniqueId: String): boolean; overload; stdcall;
    (**
     * Performs integrity check of all registered plug-ins and generates result
     * as a collection of standard report items.
     * @param pathResolver optional path resolver
     * @return integrity check report
     *)
    function checkIntegrity(pathResolver: IPathResolver): IIntegrityCheckReport;
        overload; stdcall;
    (**
     * Performs integrity check of all registered plug-ins and generates result
     * as a collection of standard report items.
     * @param pathResolver optional path resolver
     * @param includeRegistrationReport if <code>true</code>, the plug-ins
     *                                  registration report will be included
     *                                  into resulting report
     * @return integrity check report
     *)
    function checkIntegrity(pathResolver: IPathResolver; includeRegistrationReport:
        boolean): IIntegrityCheckReport; overload; stdcall;
    procedure clearPlugins; stdcall; stdcall;
    procedure configure(config: TExtendedProperties); stdcall;
    (**
     * Extracts plug-in element ID from some unique identifier.
     * @param uniqueId unique ID
     * @return element ID
     *)
    function extractId(uniqueId: String): String; stdcall;
    (**
     * Extracts plug-in ID from some unique identifier.
     * @param uniqueId unique ID
     * @return plug-in ID
     *)
    function extractPluginId(uniqueId: String): String; stdcall;
    (**
     * Extracts plug-in version identifier from some unique identifier
     * (plug-in or plug-in fragment).
     * @param uniqueId unique ID
     * @return plug-in version identifier
     *)
    function extractVersion(uniqueId: String): TVersion; stdcall;
    procedure fireEvent(data: TRegistryChangeDataImpl);
    (**
     * Utility method that recursively collects all plug-ins that depends on
     * the given plug-in.
     * @param descr descriptor of plug-in to collect dependencies for
     * @return collection of {@link PluginDescriptor plug-in descriptors}
     *         that depend on given plug-in
     *)
    function getDependingPlugins(descr: IPluginDescriptor): IIntfCollection; stdcall;
    (**
     * Returns descriptor of plug-in with given ID.
     * <br>
     * If plug-in descriptor with given ID can't be found or such plug-in
     * exists but is damaged this method have to throw an
     * {@link IllegalArgumentException}. In other words, this method
     * shouldn't return <code>null</code>.
     * @param pluginId plug-id ID
     * @return plug-in descriptor
     *)
    function getPluginDescriptor(pluginId: String): IPluginDescriptor; stdcall;
    (**
     * Returns collection of descriptors of all plug-ins that was successfully
     * populated by this registry.
     * @return collection of {@link PluginDescriptor} objects
     *)
    function getPluginDescriptors: IIntfCollection; stdcall;
    (**
     * Returns collection of descriptors of all plug-in fragments that was
     * successfully populated by this registry.
     * @return collection of {@link PluginFragment} objects
     *)
    function getPluginFragments: IIntfCollection;stdcall;
    (**
     * Checks if extension point exists and is in valid state. If this method
     * returns <code>true</code>, the method
     * {@link #getExtensionPoint(String, String)} should always return valid
     * extension point.
     * @param pluginId plug-in ID
     * @param pointId extension point ID
     * @return <code>true</code> if extension point exists and valid
     *)
    function isExtensionPointAvailable(pluginId, pointId: String): boolean;
        overload;stdcall;
    (**
     * Checks if plug-in exists and is in valid state. If this method
     * returns <code>true</code>, the method
     * {@link #getPluginDescriptor(String)} should always return valid plug-in
     * descriptor.
     * @param pluginId plug-in ID
     * @return <code>true</code> if plug-in exists and valid
     *)
    function isPluginDescriptorAvailable(pluginId: string): Boolean; stdcall;
    (**
     * Constructs unique identifier for some plug-in element from it's ID.
     * @param pluginId plug-in ID
     * @param elementId element ID
     * @return unique ID
     *)
    function makeUniqueId(pluginId, id: String): String; overload; stdcall;
    (**
     * Constructs unique identifier for plug-in with given ID.
     * @param pluginId plug-in ID
     * @param version plug-in version identifier
     * @return unique plug-in ID
     *)
    function makeUniqueId(pluginId: String; version: TVersion): String; overload; stdcall;
    (**
     * Reads basic information from a plug-in of plug-in fragment manifest.
     * @param manifest manifest data URL
     * @return manifest info
     * @throws ManifestProcessingException if manifest data can't be read
     *)
    function readManifestInfo(url: string): IManifestInfo; stdcall;
    (**
     * Registers plug-ins and plug-in fragments in this registry. Note that
     * this method not makes plug-ins available for activation
     * by any {@link PluginManager} instance as it is not aware of any manager.
     * Using this method just makes plug-in meta-data available for reading from
     * this registry.
     * <p>
     * If more than one version of the same plug-in or plug-in fragment given,
     * the only latest version should be registered. If some plug-in or plug-in
     * fragment already registered it should be ignored by this method. Client
     * application have to un-register such plug-ins first before registering
     * their newest versions.
     * @param manifests array of manifest locations
     * @return map where keys are URL's and values are registered plug-ins
     *         or plug-in fragments, URL's for unprocessed manifests are
     *         not included
     * @see PluginManager#registerPlugins(Map)
     *)
    function register(manifests : IStrList): IStrIntfMap; stdcall;
    (**
     * Registers plug-in registry change event listener. If given listener has
     * been registered before, this method should throw an
     * {@link IllegalArgumentException}.
     * @param listener new registry change event listener
     *)
    procedure registerListener(listener: IRegistryChangeListener); stdcall;
    function toString: string; override;
    (**
     * Unregisters plug-ins and plug-in fragments with given ID's
     * (including depending plug-ins and plug-in fragments).
     * @param ids ID's of plug-ins and plug-in fragments to be
     *        unregistered
     * @return collection of UID's of actually unregistered plug-ins and plug-in
     *         fragments
     *)
    function unregister(ids: TStrings): IStrCollection; stdcall;
    (**
     * Unregisters registry change event listener. If given listener hasn't been
     * registered before, this method should throw an
     * {@link IllegalArgumentException}.
     * @param listener registered listener
     *)
    procedure unregisterListener(listener: IRegistryChangeListener); stdcall;
  public
    constructor Create; override;
    (**
     * @return plug-ins registration report for this registry
     *)
    function getRegistrationReport: IIntegrityCheckReport; stdcall;
  end;

  TLibraryImpl = class(TPluginElementImpl, ILibrary)
  private
    exportList: IStrList;
    model: TModelLibrary;
  protected
    function getExports: IStrCollection; stdcall;
    function getPath: string; stdcall;
    function getUniqueId: string; stdcall;
    function getVersion: TVersion; stdcall;
    function isCodeLibrary: Boolean; stdcall;
    function toString: string; override;
  public
    constructor Create(descr: TPluginDescriptorImpl; aFragment:
        TPluginFragmentImpl; aModel: TModelLibrary);
  end;

  TManifestInfoImpl = class(TRdLogPersistent, IManifestInfo)
  private
    FOwned: Boolean;
    model: TModelManifestInfo;
  protected
    destructor Destroy; override;
     (**
       * @return plug-in or plug-in fragment identifier
       *)
    function getId: string; stdcall;
      (**
       * @return plug-in version matching rule or <code>null</code> if this
       *         info is for plug-in manifest
       *)
    function getMatchingRule: string; stdcall;
      (**
       * @return plug-in identifier this, fragment contributes to or
       *         <code>null</code> if this info is for plug-in manifest
       *)
    function getPluginId: string; stdcall;
      (**
       * @return plug-in version identifier, this fragment contributes to or
       *         <code>null</code> if this info is for plug-in manifest
       *)
    function getPluginVersion: TVersion; stdcall;
      (**
       * @return plug-in or plug-in fragment vendor
       *)
    function getVendor: string; stdcall;
      (**
       * @return plug-in or plug-in fragment version identifier
       *)
    function getVersion: TVersion; stdcall;
  public
    constructor Create(aModel: TModelManifestInfo; aOwned: Boolean = True);
  end;

implementation

uses
  SysUtils,
  RDExtensionImpl, RDExtensionPointImpl;

constructor TReferenceImpl.Create(aDoc: IDocumentation; aModel:
    TModelDocumentationReference);
begin
  inherited Create;
  doc:= aDoc;
  modelRef := aModel;
  log.debug('object instantiated: ' + Self.ClassName); //$NON-NLS-1$
end;

function TReferenceImpl.getCaption: String;
begin
  Result := modelRef.caption;
end;

function TReferenceImpl.getDeclaringIdentity: IIdentity;
begin
  Result := doc.getDeclaringIdentity;
end;

function TReferenceImpl.getRef: String;
begin
  Result := modelRef.path;
end;

constructor TDocumentationImpl.Create(anIdentity: IIdentity; aModel:
    TModelDocumentation);
var
  it: IIterator;
begin
  inherited Create;
  identity := anIdentity;
  model := aModel;

  references := TArrayList.Create(model.references.Size);
  it := model.references.First;

  while it.HasNext do
  begin
    //(model.references as ICollection).Add(
    references.Add(
      TReferenceImpl.Create(Self, TModelDocumentationReference(it.Next)));     
  end;

//  references = Collections.unmodifiableList(references);
  if log.IsDebugEnabled then
    log.debug('object instantiated: ' + Self.ClassName); //$NON-NLS-1$
end;

destructor TDocumentationImpl.Destroy;
begin
  inherited;
end;

function TDocumentationImpl.getCaption: string;
begin
  Result := model.caption;
end;

function TDocumentationImpl.getDeclaringIdentity: IIdentity;
begin
  Result := identity;
end;

function TDocumentationImpl.getReferences: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(references);
end;

function TDocumentationImpl.getText: string;
begin
  Result := model.text;
end;

constructor TPluginDescriptorImpl.Create(aRegistry: IPluginRegistry; aModel:
    TModelPluginDescriptor; aLocation: string);
begin
  inherited Create(aModel.id);
  registry := aRegistry;
  model := aModel;
  location := aLocation;

//  if Trim(model.PluginClassName) = '' then
//    model.PluginClassName := '';

  if Trim(model.docsPath) = '' then
    model.docsPath := 'docs';

  if (model.documentation <> nil) then
    doc := TDocumentationImpl.Create(Self, model.documentation);

  attributes := TLinkedList.Create;//(nil, False);
  fragments := TLinkedList.Create(nil, False);
  pluginPrerequisites := TStrHashMap.Create;//(16, False);
  libraries := TStrHashMap.Create;//(16, False);
  extensionPoints := TStrHashMap.Create;//(16, False);
  extensions := TStrHashMap.Create;//(16, False);

  processAttributes(nil, model);
  processPrerequisites(nil, model);
  processLibraries(nil, model);
  processExtensionPoints(nil, model);
  processExtensions(nil, model);
  
  if log.IsDebugEnabled then
    log.debug('object instantiated: ' + Self.ClassName); //$NON-NLS-1$
end;

destructor TPluginDescriptorImpl.Destroy;
begin
  FreeAndNil(model);
  inherited;
end;

{ TPluginDescriptorImpl }

function TPluginDescriptorImpl.getAttribute(id: String): IPluginAttribute;
var
  attr,
  attrResult: IPluginAttribute;
  it: IIterator;
begin
  it := attributes.First;
  while it.HasNext do
  begin
    Supports(it.Next, IPluginAttribute, attr);
//    attr := IPluginAttribute(it.Next);
    if Rcl.Str.IsEqual(attr.getId, id) then
    begin
      if attrResult = nil then
        attrResult := attr
      else
        raise EIllegalArgument.Create(
          'more than one attribute with ID ' + id
                  + ' defined in plug-in ' + getUniqueId);
    end;
  end;
  Result := attrResult;
end;

function TPluginDescriptorImpl.getAttributes: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(attributes);
end;

function TPluginDescriptorImpl.getAttributes(id: String): IIntfCollection;
var
  listResult: IIntfList;
  it: IIterator;
  param: IPluginAttribute;
//  paramObj: TObject;
begin
  listResult := TIntfLinkedList.Create;
  it := attributes.First;
  while it.HasNext do
  begin
//    paramObj := it.Next;
    Supports(it.Next, IPluginAttribute, param);
//    param := IPluginAttribute(it.Next);
    if Rcl.Str.IsEqual(param.getId, id) then
      listResult.Add(param);
  end;
  Result := listResult;
end;

function TPluginDescriptorImpl.getDocsPath: String;
begin
  Result := model.docsPath;
end;

function TPluginDescriptorImpl.getDocumentation: IDocumentation;
begin
  Result := doc;
end;

function TPluginDescriptorImpl.getExtension(id: string): IExtension;
var
  obj: TObject;
begin
  obj := extensions.Items[id];
  if obj <> nil then
    obj.GetInterface(IExtension, Result);
end;

function TPluginDescriptorImpl.getExtensionPoint(id: string): IExtensionPoint;
var
  obj: TObject;
begin
  obj := extensionPoints.Items[id];
  if obj <> nil then
    obj.GetInterface(IExtensionPoint, Result);
end;

function TPluginDescriptorImpl.getExtensionPoints: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(extensionPoints.Values);
end;

function TPluginDescriptorImpl.getExtensions: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(extensions.Values);
end;

function TPluginDescriptorImpl.getFragments: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(fragments);
end;

function TPluginDescriptorImpl.getLibraries: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(libraries.Values);
end;

function TPluginDescriptorImpl.getLibrary(id: string): ILibrary;
begin
  Supports(libraries.GetValue(id), ILibrary, Result);
//  Result := ILibrary(libraries.GetValue(id));
end;

function TPluginDescriptorImpl.getLocation: string;
begin
  Result := location;
end;

function TPluginDescriptorImpl.getPath: string;
begin
  Result := Rcl.FileUtils.GetFilePath(Location)
end;

function TPluginDescriptorImpl.getPluginClassName: String;
begin
  Result := model.PluginClassName;
end;

function TPluginDescriptorImpl.getPrerequisite(id: string): IPluginPrerequisite;
begin
  Supports(pluginPrerequisites.GetValue(id), IPluginPrerequisite, Result);
//  Result := pluginPrerequisites.GetValue(id) as IPluginPrerequisite;
end;

function TPluginDescriptorImpl.getPrerequisites: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(pluginPrerequisites.Values);
end;

function TPluginDescriptorImpl.getRegistry: IPluginRegistry;
begin
  Result := registry;
end;

function TPluginDescriptorImpl.getUniqueId: String;
begin
  Result := registry.makeUniqueId(getId, model.version);
end;

function TPluginDescriptorImpl.getVendor: String;
begin
  Result := model.vendor;
end;

function TPluginDescriptorImpl.getVersion: TVersion;
begin
  Result := model.version;
end;

function TPluginDescriptorImpl.isEqualTo(idt: IIdentity): Boolean;
var
  other: TPluginDescriptorImpl;
begin
  if (not (TObject(idt) is TPluginDescriptorImpl)) then
  begin
    Result := False;
    Exit;
  end;
  
  other := TPluginDescriptorImpl(idt);
  Result := (getLocation = other.getLocation)
          and (getUniqueId = other.getUniqueId);
end;

procedure TPluginDescriptorImpl.processAttributes(fragment:
    TPluginFragmentImpl; modelManifest: TModelPluginManifest);
var
  it: IIterator;
begin
  it := modelManifest.attributes.First;
  while it.HasNext do
  begin
    attributes.Add(TPluginAttributeImpl.Create(Self, fragment,
      TModelAttribute(it.Next), nil));
  end;
end;

procedure TPluginDescriptorImpl.processExtensionPoints(fragment:
    TPluginFragmentImpl; modelManifest: TModelPluginManifest);
var
  it: IIterator;
  extensionPoint: IExtensionPoint;
begin
  it := modelManifest.extensionPoints.First;
  while it.HasNext do
  begin
    extensionPoint := TExtensionPointImpl.Create(Self, fragment,
      TModelExtensionPoint(it.Next));
    if (extensionPoints.containsKey(IExtensionPoint(extensionPoint).getId)) then
        raise EManifestProcessing.Create(
                PACKAGE_NAME,
                'duplicateExtensionPoints', [
                    extensionPoint.getId, getId]);

    extensionPoints.PutValue(extensionPoint.getId, extensionPoint.Implementor);
  end;
end;

procedure TPluginDescriptorImpl.processExtensions(fragment:
    TPluginFragmentImpl; modelManifest: TModelPluginManifest);
var
  it: IIterator;
  extension: IExtension;
  extensionObj: TExtensionImpl;
begin
  it := modelManifest.extensions.First;
  while it.HasNext do
  begin
    extensionObj := TExtensionImpl.Create(Self, fragment, TModelExtension(it.Next));
    extension :=  extensionObj as IExtension;
    if extensions.ContainsKey(extension.getId) then
      raise EManifestProcessing.Create(PACKAGE_NAME,
        'duplicateExtensions', [extension.getId, getId]);
    if (not Rcl.Str.IsEqual(getId, extension.getExtendedPluginId))
            and (not pluginPrerequisites.containsKey(
                    extension.getExtendedPluginId)) then
      raise EManifestProcessing.Create(PACKAGE_NAME,
              'pluginNotDeclaredInPrerequisites', [
                  extension.getExtendedPluginId, extension.getId,
                  getId]);
    
    extensions.PutValue(extension.getId, extensionObj);
  end;
  //extensions = Collections.unmodifiableMap(extensions);
end;

procedure TPluginDescriptorImpl.processLibraries(fragment: TPluginFragmentImpl;
    modelManifest: TModelPluginManifest);
var
  it: IIterator;
  lib: TLibraryImpl;
begin
  it := modelmanifest.libraries.First;
  while it.HasNext do
  begin
    lib := TLibraryImpl.Create(Self, fragment, TModelLibrary(it.Next));
    if (libraries.containsKey(lib.getId)) then
      raise EManifestProcessing.Create(
                PACKAGE_NAME,
                'duplicateLibraries', [lib.getId, getId]);      
    libraries.PutValue(lib.getId, lib);
  end;
end;

procedure TPluginDescriptorImpl.processPrerequisites(fragment:
    TPluginFragmentImpl; modelManifest: TModelPluginManifest);
var
  it: IIterator;
  pluginPrerequisite: TPluginPrerequisiteImpl;
begin
  it := modelManifest.prerequisites.First;
  while it.HasNext do
  begin
    pluginPrerequisite := TPluginPrerequisiteImpl.Create(Self,
      fragment, TModelPrerequisite(it.Next));
    if pluginPrerequisites.ContainsKey(pluginPrerequisite.getPluginId) then
    begin
      raise EManifestProcessing.Create('PluginRegistryImpl', 'duplicateImports',
        [pluginPrerequisite.getPluginId, getId]);
    end;
    pluginPrerequisites.PutValue(pluginPrerequisite.getPluginId,
        pluginPrerequisite);    
  end;
end;

procedure TPluginDescriptorImpl.registerFragment(fragment: TPluginFragmentImpl);
begin
  fragments.add(fragment);
  processAttributes(fragment, fragment.getModel);
  processPrerequisites(fragment, fragment.getModel);
  processLibraries(fragment, fragment.getModel);
  processExtensionPoints(fragment, fragment.getModel);
  processExtensions(fragment, fragment.getModel);
end;

function TPluginDescriptorImpl.toString: string;
begin
  Result := '{PluginDescriptor: uid=' + getUniqueId + '}';
end;

procedure TPluginDescriptorImpl.unregisterFragment(fragment:
    TPluginFragmentImpl);
var
  it: IIterator;
  fragment1,
  fragment2: IPluginFragment;
  pluginAttr: IPluginAttribute;
  pluginPre: IPluginPrerequisite;
begin
  fragment1 := fragment;

  // removing attributes
  it := attributes.First;
  while it.HasNext do
  begin
    Supports(it.Next, IPluginAttribute, pluginAttr);
    fragment2 := pluginAttr.getDeclaringPluginFragment;
    if fragment1 = fragment2 then
      it.Remove;
  end;

  // removing prerequisites
  it := pluginPrerequisites.Values.First;
  while it.HasNext do
  begin
    Supports(it.Next, IPluginPrerequisite, pluginPre);
    fragment2 := pluginPre.getDeclaringPluginFragment;
    if fragment1 = fragment2 then
      it.Remove;
  end;

  fragments.remove(fragment);
end;


{ TPluginFragmentImpl }

constructor TPluginFragmentImpl.Create(aRegistry: IPluginRegistry; aModel:
    TModelPluginFragment; aLocation: string);
begin
    inherited Create(aModel.id);
    registry := aRegistry;
    model := aModel;
    location := aLocation;

//    if Rcl.Str.IsEmpty(model.pluginId, True) then
//        raise EManifestProcessing.Create(
//                PluginRegistryImpl.PACKAGE_NAME,
//                "fragmentPliginIdIsBlank", getId()); //$NON-NLS-1$
//    }
//    if (getId().equals(model.getPluginId())) {
//        throw new ManifestProcessingException(
//                PluginRegistryImpl.PACKAGE_NAME,
//                "invalidFragmentPluginId", getId()); //$NON-NLS-1$
//    }
//    if ((model.getDocsPath() == null)
//            || (model.getDocsPath().trim().length() == 0)) {
//        model.setDocsPath("docs"); //$NON-NLS-1$
//    }
//    if (model.getDocumentation() != null) {
//        doc = new DocumentationImpl(this, model.getDocumentation());
//    }
//    log.debug("object instantiated: " + this); //$NON-NLS-1$

end;

destructor TPluginFragmentImpl.Destroy;
begin
  inherited;
  // TODO -cMM: TPluginFragmentImpl.Destroy default body inserted
end;

function TPluginFragmentImpl.getDocsPath: String;
begin

end;

function TPluginFragmentImpl.getDocumentation: IDocumentation;
begin

end;

function TPluginFragmentImpl.getLocation: string;
begin

end;

function TPluginFragmentImpl.getModel: TModelPluginFragment;
begin
  Result := nil;
end;

function TPluginFragmentImpl.getPluginId: String;
begin

end;

function TPluginFragmentImpl.getPluginVersion: TVersion;
begin

end;

function TPluginFragmentImpl.getRegistry: IPluginRegistry;
begin

end;

function TPluginFragmentImpl.getUniqueId: String;
begin

end;

function TPluginFragmentImpl.getVendor: String;
begin

end;

function TPluginFragmentImpl.getVersion: TVersion;
begin

end;

function TPluginFragmentImpl.matches(descr: IPluginDescriptor): Boolean;
begin

end;   

function TPluginFragmentImpl.toString: string;
begin
  Result := '{PluginFragment: uid=' + getUniqueId + '}'; ;
end;

constructor TPluginAttributeImpl.Create(descr: TPluginDescriptorImpl;
    aFragment: TPluginFragmentImpl; aModel: TModelAttribute; aSuperAttribute:
    TPluginAttributeImpl );
var
  it: IIterator;
begin
  inherited Create(descr, aFragment, aModel.Id, aModel.documentation);
  model := aModel;
  superAttribute := aSuperAttribute;
//  if (model.value = '') then
//      model.setValue(""); //$NON-NLS-1$
//  }
  subAttributes := TArrayList.Create(model.attributes.Size);
  it := model.attributes.First;
  while it.HasNext do
    subAttributes.Add(TPluginAttributeImpl.Create(descr, aFragment,
              TModelAttribute(it.next), Self));

  subAttributes := subAttributes;
  log.debug('object instantiated: ' + Self.ClassName); //$NON-NLS-1$
end;

function TPluginAttributeImpl.getSubAttribute(id: String): IPluginAttribute;
var
  param, pluginAttribute: TPluginAttributeImpl;
  it: IIterator;
begin
  pluginAttribute := nil;
  it := subAttributes.First;
  while it.HasNext do
  begin
    param := TPluginAttributeImpl(it.Next);
    if Rcl.Str.IsEqual(param.getId, id) then
    begin
      if pluginAttribute = nil then
        pluginAttribute := param
      else
        raise EIllegalArgument.Create('more than one attribute with ID ' + id
                  + ' defined in plug-in '
                  + getDeclaringPluginDescriptor.getUniqueId);
    end;     
  end;
  Result := pluginAttribute;
end;

function TPluginAttributeImpl.getSubAttributes: IIntfCollection;
begin
  Result := Collections.ToIntfCollection(subAttributes);
end;

function TPluginAttributeImpl.getSubAttributes(id: String): IIntfCollection;
var
//  list: IIntfList;
  it: IIterator;
  param: TPluginAttributeImpl;
begin
  Result := TIntfLinkedList.Create;
  it := subAttributes.First;
  while it.HasNext do
  begin
//    param := TPluginAttributeImpl(it.Next);
    Supports(it.Next, IPluginAttribute, param);
    if Rcl.Str.IsEqual(param.getId, id) then
      Result.Add(param);
  end;
//  Result := list;
end;

function TPluginAttributeImpl.getSuperAttribute: IPluginAttribute;
begin
  Result := superAttribute;
end;

function TPluginAttributeImpl.getValue: string;
begin
  Result := model.value;
end;

function TPluginAttributeImpl.isEqualTo(idt: IIdentity): Boolean;
var
  other: TPluginAttributeImpl;
begin
  if (not (inherited isEqualTo(idt))) then
  begin
      Result := False;
      Exit;
  end;
  
  other := TPluginAttributeImpl(idt);
  if ((getSuperAttribute = nil)
          and (other.getSuperAttribute = nil)) then
  begin
      Result := True;
      Exit;
  end;

  if ((getSuperAttribute= nil)
          or (other.getSuperAttribute = nil)) then
  begin
      Result := False;
      Exit;
  end;
  Result := (getSuperAttribute = other.getSuperAttribute);
end;

{ TPluginElementImpl }

constructor TPluginElementImpl.Create(descr: IPluginDescriptor; aFragment:
    IPluginFragment; id: String; modelDoc: TModelDocumentation);
begin
  inherited Create(id);
  descriptor := descr;
  fragment := aFragment;
  if (modelDoc <> nil) then
      doc := TDocumentationImpl.Create(Self, modelDoc);
end;  

destructor TPluginElementImpl.Destroy;
begin
  if doc <> nil then
    doc.Free;
  doc := nil;  
  inherited;
end;

function TPluginElementImpl.getDeclaringPluginDescriptor: IPluginDescriptor;
begin
  Result := descriptor;
end;

function TPluginElementImpl.getDeclaringPluginFragment: IPluginFragment;
begin
  Result := fragment;
end;

function TPluginElementImpl.getDocsPath: String;
begin
  if fragment <> nil then
    Result := fragment.getDocsPath()
  else
    Result := descriptor.getDocsPath()
end;

function TPluginElementImpl.getDocumentation: IDocumentation;
begin
  Result := doc;
end;

function TPluginElementImpl.getUniqueId: string;
begin
//
end;

function TPluginElementImpl.isEqualTo(idt: IIdentity): Boolean;
begin
  if not Rcl.Str.IsEqual(Self.ClassName, TObject(idt).ClassName) then
  begin
    Result := False;
    Exit;
  end;

  Result := (getDeclaringPluginDescriptor =
          TPluginElementImpl(idt).getDeclaringPluginDescriptor)
          and Rcl.Str.IsEqual(getId, idt.getId);
end;

constructor TPluginPrerequisiteImpl.Create(descr: TPluginDescriptorImpl;
    aFragment: TPluginFragmentImpl; aModel: TModelPrerequisite);
begin
  Self.Create;
  descriptor := descr;
  fragment := aFragment;
  model := aModel;
  if (model.pluginId = '') then
      raise EManifestProcessing.Create(
              TPluginRegistryImpl.PACKAGE_NAME,
              'prerequisitePliginIdIsBlank', descr.getId()); //$NON-NLS-1$

  if Rcl.Str.IsEqual(descr.getId, model.pluginId) then
      raise EManifestProcessing.Create(
              TPluginRegistryImpl.PACKAGE_NAME,
              'invalidPrerequisitePluginId', descr.getId()); //$NON-NLS-1$
  
  if (model.id = '') then
      model.id := 'prerequisite:' + model.pluginId; //$NON-NLS-1$

  if (model.documentation <> nil) then
      doc := TDocumentationImpl.Create(IPluginPrerequisite(Self),
          model.documentation);
  
  log.debug('object instantiated: ' + Self.ClassName); //$NON-NLS-1$
end;

destructor TPluginPrerequisiteImpl.Destroy;
begin
  inherited;
  // TODO -cMM: TPluginPrerequisiteImpl.Destroy default body inserted
end;

function TPluginPrerequisiteImpl.getDeclaringPluginDescriptor:
    IPluginDescriptor;
begin
  Result := descriptor;
end;

function TPluginPrerequisiteImpl.getDeclaringPluginFragment: IPluginFragment;
begin
  Result := fragment;
end;

function TPluginPrerequisiteImpl.getDocsPath: String;
begin
  if (fragment <> nil) then
    Result := fragment.getDocsPath
  else
    Result := descriptor.getDocsPath;
end;

function TPluginPrerequisiteImpl.getDocumentation: IDocumentation;
begin
  Result := doc;
end;

function TPluginPrerequisiteImpl.getId: string;
begin
  Result := model.id;
end;

function TPluginPrerequisiteImpl.getMatch: String;
begin
  Result := model.match;
end;

function TPluginPrerequisiteImpl.getPluginId: string;
begin
  Result := model.pluginId;
end;

function TPluginPrerequisiteImpl.getPluginVersion: TVersion;
begin
  Result := model.pluginVersion;
end;

function TPluginPrerequisiteImpl.getUniqueId: string;
begin
//
end;

function TPluginPrerequisiteImpl.isExported: boolean;
begin
  Result := model.isExported;
end;

function TPluginPrerequisiteImpl.isOptional: boolean;
begin
  Result := model.isOptional;
end;

function TPluginPrerequisiteImpl.matches: boolean;
var
  descr: IPluginDescriptor;
begin
  try
      descr := Self.descriptor.getRegistry().getPluginDescriptor(
              model.pluginId);
  except
    on iae :EIllegalArgument do
    begin
      Result := false;
      Exit;
    end;
  end;
  Result := matches(model.PluginVersion, descr.getVersion,
          model.match);
end;  

class function TPluginPrerequisiteImpl.matches(source, target: TVersion; match:
    string): Boolean;
begin
  if (source = nil) then
  begin
      Result := true;
      Exit;
  end;

  if Rcl.Str.IsEqual(MATCH_EQUAL, match) then
      Result := target.equals(source)
  else if Rcl.Str.IsEqual(MATCH_EQUAL, match) then
      Result := target.isEquivalentTo(source)
  else if Rcl.Str.IsEqual(MATCH_EQUAL, match) then
      Result := target.isCompatibleWith(source)
  else if Rcl.Str.IsEqual(MATCH_EQUAL, match) then
      Result := target.isGreaterOrEqualTo(source)
  else
      Result := target.isCompatibleWith(source);
end;

constructor TRegistryChangeDataImpl.Create;
begin
  inherited Create;
//  Owner := True;
  reset;
end;

destructor TRegistryChangeDataImpl.Destroy;
begin
  inherited;
  // TODO -cMM: TRegistryChangeDataImpl.Destroy default body inserted
end;

function TRegistryChangeDataImpl.addedExtensions: IStrSet;
begin
  Result := addedExtensionsMap.KeySet
end;

function TRegistryChangeDataImpl.addedExtensions(extensionPointUid: String):
    IStrSet;
begin
  Result := getMapKeySet(addedExtensionsMap, extensionPointUid);
end;

function TRegistryChangeDataImpl.addedPlugins: IStrSet;
begin
  Result := addedPluginsSet;
end;

procedure TRegistryChangeDataImpl.afterEventFire;
begin
  reset;
end;

procedure TRegistryChangeDataImpl.beforeEventFire;
begin
  addedPluginsSet := Collections.unmodifiableSet(addedPluginsSet);
  removedPluginsSet := Collections.unmodifiableSet(removedPluginsSet);
  modifiedPluginsSet := Collections.unmodifiableSet(modifiedPluginsSet);
  addedExtensionsMap := Collections.unmodifiableMap(addedExtensionsMap);
  removedExtensionsMap := Collections.unmodifiableMap(removedExtensionsMap);
  modifiedExtensionsMap :=
      Collections.unmodifiableMap(modifiedExtensionsMap);

end;

procedure TRegistryChangeDataImpl.dump;
var
  buf: TStrings;
  it: IStrIterator;
begin
  if (not log.isDebugEnabled) then
      Exit;

  buf := TStringList.Create;
  try
    buf.append('PLUG-IN REGISTRY CHANGES DUMP:'); //$NON-NLS-1$
    buf.append('-------------- DUMP BEGIN -----------------'); //$NON-NLS-1$
    buf.append('  Added plug-ins: ' + IntToStr(addedPlugins.size));
    it := addedPlugins.First;
    while it.HasNext do
      buf.append('    ' + it.next);

    buf.append('  Removed plug-ins: ' + IntToStr(removedPlugins.size));
    it := removedPlugins.First;
    while it.HasNext do
      buf.append('    ' + it.next);

    buf.append('  Modified plug-ins: ' + IntToStr(modifiedPlugins.size));
    it := modifiedPlugins.First;
    while it.HasNext do
      buf.append('    ' + it.next);

    buf.append('  Added extensions: ' + IntToStr(addedExtensions.size));
    it := addedExtensions.First;
    while it.HasNext do
      buf.append('    ' + it.next);

    buf.append('  Removed extensions: ' + IntToStr(removedExtensions.size));
    it := removedExtensions.First;
    while it.HasNext do
      buf.append('    ' + it.next);

    buf.append('  Modified extensions: ' + IntToStr(modifiedExtensions.size));
    it := modifiedExtensions.First;
    while it.HasNext do
      buf.append('    ' + it.next);

//    buf.append('Memory TOTAL/FREE/MAX: ');
//        .append(Runtime.getRuntime.totalMemory)
//        .append('/') //$NON-NLS-1$
//        .append(Runtime.getRuntime.freeMemory)
//        .append('/') //$NON-NLS-1$
//        .append(Runtime.getRuntime.maxMemory)
//        .append('\r\n'); //$NON-NLS-1$
    buf.append('-------------- DUMP END -----------------'); //$NON-NLS-1$
    log.debug(buf.Text);
  finally
    buf.Free;
  end;

end;

function TRegistryChangeDataImpl.getMapKeySet(aMap: IStrStrMap; aValue:
    string): IStrSet;
var
  it : IStrIterator;
begin
  Result := TStrHashSet.Create;
  it := aMap.Values.First;
  while it.HasNext do
    if Rcl.Str.IsEqual(it.Next, aValue) then
      Result.Add(addedExtensionsMap.KeyOfValue(aValue));
end;

function TRegistryChangeDataImpl.modifiedExtensions: IStrSet;
begin
  Result := modifiedExtensionsMap.KeySet;
end;

function TRegistryChangeDataImpl.modifiedExtensions(extensionPointUid: String):
    IStrSet;
begin
  Result := getMapKeySet(modifiedExtensionsMap, extensionPointUid);
end;

function TRegistryChangeDataImpl.modifiedPlugins: IStrSet;
begin
  Result := modifiedPluginsSet;
end;

procedure TRegistryChangeDataImpl.putAddedExtension(extensionUid,
    extensionPointUid: string);
begin
  addedExtensionsMap.Items[extensionUid] := extensionPointUid;
end;

procedure TRegistryChangeDataImpl.putModifiedExtension(extensionUid,
    extensionPointUid: string);
begin
  modifiedExtensionsMap.Items[extensionUid] := extensionPointUid;
end;

procedure TRegistryChangeDataImpl.putRemovedExtension(extensionUid,
    extensionPointUid: string);
begin
  removedExtensionsMap.Items[extensionUid] := extensionPointUid;
end;

function TRegistryChangeDataImpl.removedExtensions: IStrSet;
begin
  Result := removedExtensionsMap.KeySet;
end;

function TRegistryChangeDataImpl.removedExtensions(extensionPointUid: String):
    IStrSet;
begin
  Result := getMapKeySet(removedExtensionsMap, extensionPointUid);
end;

function TRegistryChangeDataImpl.removedPlugins: IStrSet;
begin
  Result := removedPluginsSet;
end;

procedure TRegistryChangeDataImpl.reset;
begin
  addedPluginsSet := TStrHashSet.Create;
  removedPluginsSet := TStrHashSet.Create;
  modifiedPluginsSet := TStrHashSet.Create;
  addedExtensionsMap := TStrStrHashMap.Create;
  removedExtensionsMap := TStrStrHashMap.Create;
  modifiedExtensionsMap := TStrStrHashMap.Create;
end;

constructor TPluginRegistryImpl.Create;
begin
  inherited Create(True);
  
  registrationReport := TArrayList.Create; // <ReportItemImpl>
  registeredPlugins := TStrHashMap.Create;//(16, False);// <pluginId, PluginDescriptorImpl>
  registeredFragments := TStrHashMap.Create;//(16, False); // <fragmentId, PluginFragmentImpl>
  listeners := TIntfLinkedList.Create; // <EventListener>
  stopOnError := False;
  isValidating := True;

  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR, 'registryStart', nil)); //$NON-NLS-1$
end;


{$IFNDEF DELPHI9_UP}
class function TPluginRegistryImpl.PACKAGE_NAME: string;
begin
  Result := 'TPluginRegistryImpl';
end;
{$ENDIF}

destructor TPluginRegistryImpl.Destroy;
begin
  if manifestParser <> nil then
    FreeAndNil(manifestParser);
  inherited;
end;

function TPluginRegistryImpl.checkIntegrity(pathResolver: IPathResolver):
    IIntegrityCheckReport;
begin
  Result := checkIntegrity(pathResolver, false);
end;

function TPluginRegistryImpl.checkIntegrity(pathResolver: IPathResolver;
    includeRegistrationReport: boolean): IIntegrityCheckReport;
var
  intergityCheckReport: TIntegrityChecker;
  list: IList;
begin
  if includeRegistrationReport then
    list := registrationReport
  else
    list := Collections.EMPTY_LIST;
    list := nil;

  intergityCheckReport := TIntegrityChecker.Create(Self, list);
  intergityCheckReport.doCheck(pathResolver);
  Result := intergityCheckReport;
end;

procedure TPluginRegistryImpl.checkVersions(plugins: IStrMap);
var
  versions: IStrMap;
  version: TVersion;

  urls: IStrStrMap;
  url1: string;
  urlObj: TObject;
  toBeRemovedUrls: IStrSet;
  it: IStrIterator;
//  intfit: IIntfIterator;
  url: string;
  model: TModelPluginManifest;
begin 
  versions := TStrHashMap.Create(16, False); //<ID, Version>
  urls := TStrStrHashMap.Create; //<ID, URL>
  
  toBeRemovedUrls := TStrHashSet.Create; //<URL>
  it := plugins.KeySet.First;
  while it.HasNext do
  begin
    url := it.Next;
    model := TModelPluginManifest(plugins.Items[url]);
    if registeredPlugins.ContainsKey(model.id) then
    begin
      if stopOnError then
        raise EManifestProcessing.Create(PACKAGE_NAME,
                            'duplicatePlugin', //$NON-NLS-1$
                            model.id);
      plugins.Remove(url);
//      registrationReport.add(TReportItemImpl.Create(nil,
//              SEVERITY_ERROR, nil,
//              ERROR_MANIFEST_PROCESSING_FAILED,
//              'duplicatedPluginId', model.id)); //$NON-NLS-1$
      continue;
    end;

    if (registeredFragments.containsKey(model.id)) then
    begin
        if (stopOnError) then
          raise  EManifestProcessing.Create(PACKAGE_NAME,
                    'duplicatePluginFragment', //$NON-NLS-1$
                    model.id);

        plugins.Remove(url);
//        registrationReport.add(TReportItemImpl.Create(nil,
//                SEVERITY_ERROR, nil,
//                ERROR_MANIFEST_PROCESSING_FAILED,
//                'duplicatedFragmentId', model.id)); //$NON-NLS-1$
        continue;
    end;

    version := TVersion(versions.Items[model.id]);
    url1 := urls.GetValue(model.id);
    if version = nil then
    begin
      versions.Items[model.id] := model.version;
      urls.PutValue(model.id, url);
      continue;
    end;
    if version.compareTo(model.version) < 0 then
    begin
      toBeRemovedUrls.Add(url1);
      versions.Items[model.id] := model.version;
      urls.PutValue(model.id, url);
    end
    else toBeRemovedUrls.Add(url);

  end;

  versions.Clear;
  urls.Clear;
  it := toBeRemovedUrls.First;
  while it.HasNext do
  begin
    plugins.Remove(it.Next);
  end;
  toBeRemovedUrls.Clear;
end;

procedure TPluginRegistryImpl.clearPlugins;
begin
  registeredPlugins.Clear;
  registeredFragments.Clear;
end;

procedure TPluginRegistryImpl.configure(config: TExtendedProperties);
begin
  stopOnError := Rcl.Str.IsEqual('true',
          config.getProperty('stopOnError', 'false')); //$NON-NLS-1$ //$NON-NLS-2$
  isValidating := not Rcl.Str.IsEqual('false',
          config.getProperty('isValidating', 'true')); //$NON-NLS-1$ //$NON-NLS-2$
  manifestParser := TRdManifestParser.Create(isValidating);
  log.info('configured, stopOnError=' + BoolToStr(stopOnError, True) //$NON-NLS-1$
          + ', isValidating=' + BoolToStr(isValidating, True)); //$NON-NLS-1$
end;

procedure TPluginRegistryImpl.dump;
var
  buf: TStrings;
  it: IIterator;
  intf: IRdInterface;
begin
  if (not log.isDebugEnabled) then
    Exit;
  buf := TStringList.Create;
  try
    buf.append('PLUG-IN REGISTRY DUMP:'); //$NON-NLS-1$
    buf.append('-------------- DUMP BEGIN -----------------'); //$NON-NLS-1$
    buf.append('  Plug-ins: ' + IntToStr(registeredPlugins.size)); //$NON-NLS-1$

    it := registeredPlugins.Values.First;
    while it.HasNext do
    begin
      if Supports(it.Next, IRdInterface, intf) then
        buf.Append('    ' + intf.toString);
    end;
    buf.append('  Fragments: ' + IntToStr(registeredFragments.size)); //$NON-NLS-1$
    it := registeredFragments.Values.First;
    while it.HasNext do
    begin
      if Supports(it.Next, IRdInterface, intf) then
        buf.Append('    ' + intf.toString);
    end;

  //  buf.append('Memory TOTAL/FREE/MAX: ') //$NON-NLS-1$
  //    .append(Runtime.getRuntime().totalMemory())
  //    .append('/') //$NON-NLS-1$
  //    .append(Runtime.getRuntime().freeMemory())
  //    .append('/') //$NON-NLS-1$
  //    .append(Runtime.getRuntime().maxMemory())
  //    .append('\r\n'); //$NON-NLS-1$
    buf.append('-------------- DUMP END -----------------'); //$NON-NLS-1$
    log.debug(buf.Text);
  finally
    buf.Free;
  end;

end;

function TPluginRegistryImpl.extractId(uniqueId: String): String;
var
  p: Integer;
begin
  p := Pos(UNIQUE_SEPARATOR, uniqueId);
  if ((p <= 0) or (p >= Length(uniqueId))) then
    raise EIllegalArgument.Create('invalid unique ID - ' //$NON-NLS-1$
              + uniqueId);

  Result := Copy(uniqueId, p + 1, Length(uniqueId));
end;

function TPluginRegistryImpl.extractPluginId(uniqueId: String): String;
var
  p: Integer;
begin
  p := Pos(UNIQUE_SEPARATOR, uniqueId);
  if ((p <= 0) or (p >= Length(uniqueId))) then
    raise EIllegalArgument.Create('invalid unique ID - ' //$NON-NLS-1$
              + uniqueId);

  Result := Copy(uniqueId, 1, p - 1);
end;

function TPluginRegistryImpl.extractVersion(uniqueId: String): TVersion;
var
  p: Integer;
begin
  p := Pos(UNIQUE_SEPARATOR, uniqueId);
  if ((p <= 0) or (p >= Length(uniqueId))) then
    raise EIllegalArgument.Create('invalid unique ID - ' //$NON-NLS-1$
              + uniqueId);

  Result := TVersion.parse(Copy(uniqueId, p + 1, Length(uniqueId)));
end;

procedure TPluginRegistryImpl.fireEvent(data: TRegistryChangeDataImpl);
var
//  arr: array of IRegistryChangeListener;
  it: IIntfIterator;
begin
  data.dump();
  if (listeners.isEmpty) then
    Exit;

  // make local copy
//  SetLength(arr, listeners.size);
//  arr = listeners.toArray(
//        new RegistryChangeListener[listeners.size()]);
  data.beforeEventFire();
  log.debug('propagating registry change event'); //$NON-NLS-1$
  it := listeners.First;
  while it.HasNext do
    (it.Next as IRegistryChangeListener).registryChanged(data);
  log.debug('registry change event propagated');
  data.afterEventFire();
end;

function TPluginRegistryImpl.getDependingPlugins(descr: IPluginDescriptor):
    IIntfCollection;
var
  map: IStrIntfMap;
  it, it2, it3: IIntfIterator;
  dependedDescr: IPluginDescriptor;
//  dependedDescrObj: TObject;
  pre: IPluginPrerequisite;
  descriptor: IPluginDescriptor;
//  descriptorObj: TObject;
begin
//  map := TStrHashMap.Create;
  map := TStrIntfHashMap.Create;
  it := getPluginDescriptors.First;
  while it.HasNext do
  begin
//    dependedDescrObj := it.Next;
    Supports(it.Next, IPluginDescriptor, dependedDescr);
    if Rcl.Str.IsEqual(dependedDescr.getId, descr.getId) then
      Continue;
    it2 := dependedDescr.getPrerequisites.First;
    while it2.HasNext do
    begin
      Supports(it2.Next, IPluginPrerequisite, pre);
//      pre := it2.Next as IPluginPrerequisite;
      if (not Rcl.Str.IsEqual(pre.getPluginId, descr.getId)) or (not pre.matches) then
        Continue;

      if not map.ContainsKey(dependedDescr.getId) then
      begin
        map.PutValue(dependedDescr.getId, dependedDescr);
        it3 := getDependingPlugins(dependedDescr).First;
        while it3.HasNext do
        begin
//          descriptorObj := it3.Next;
          Supports(it3.Next, IPluginDescriptor, descriptor);
//          descriptor := it3.Next as IPluginDescriptor;
          if not map.ContainsKey(descriptor.getId) then
            map.PutValue(descriptor.getId, descriptor);
        end;
      end;
      Break;
    end;
  end;
  Result := map.Values;
end;


function TPluginRegistryImpl.getExtensionPoint(uniqueId: String):
    IExtensionPoint;
begin
  Result := getExtensionPoint(extractPluginId(uniqueId),
          extractId(uniqueId));
end;

function TPluginRegistryImpl.getExtensionPoint(pluginId, pointId: string):
    IExtensionPoint;
var
  descriptor: IPluginDescriptor;
  point: IExtensionPoint;
  it: IIntfIterator;
begin 
  descriptor := getPluginDescriptor(pluginId);
  if (descriptor = nil) then
      raise EIllegalArgument.Create('unknown extension point ID - ' //$NON-NLS-1$
          + makeUniqueId(pluginId, pointId));
  it := descriptor.getExtensionPoints.First;
  while it.HasNext do
  begin
    Supports(it.Next, IExtensionPoint, point);
//    point := it.Next as IExtensionPoint;
    if Rcl.Str.IsEqual(point.getId, pointId) then
    begin
      if point.isValid then
      begin
        Result := point;
        Exit;
      end;
      log.warn('extension point ' + point.getUniqueId   //$NON-NLS-1$
              + ' is invalid and ignored by registry'); //$NON-NLS-1$
      Break;
    end;
  end;

  raise EIllegalArgument.Create('unknown extension point ID - ' //$NON-NLS-1$
          + makeUniqueId(pluginId, pointId));
end;

function TPluginRegistryImpl.getPluginDescriptor(pluginId: String):
    IPluginDescriptor;
var
  descriptor: IPluginDescriptor;
  descObj: TObject;
begin
//  descObj := registeredPlugins.GetValue(pluginId);
//  if descObj <> nil then
//    descObj.GetInterface(IPluginDescriptor, descriptor);

  Supports(registeredPlugins.GetValue(pluginId),
    IPluginDescriptor, descriptor);

  if (descriptor = nil) then
    raise EIllegalArgument.Create('unknown plug-in ID - ' //$NON-NLS-1$
              + pluginId);
  Result := descriptor;
end;

function TPluginRegistryImpl.getPluginDescriptors: IIntfCollection;
begin
  if registeredPlugins.isEmpty then
    Result := nil
  else
    Result := Collections.unmodifiableCollection(
                  Collections.ToIntfCollection(registeredPlugins.values));
end;

function TPluginRegistryImpl.getPluginFragments: IIntfCollection;
begin
  if registeredFragments.isEmpty then
    Result := nil
  else
    Result := Collections.unmodifiableCollection(
                  Collections.ToIntfCollection(registeredFragments.values));
end;

function TPluginRegistryImpl.getRegistrationReport: IIntegrityCheckReport;
begin
  Result := TIntegrityChecker.Create(Self, registrationReport);
end;

function TPluginRegistryImpl.isExtensionPointAvailable(uniqueId: String):
    boolean;
begin
  Result := isExtensionPointAvailable(extractPluginId(uniqueId),
          extractId(uniqueId));
end;

function TPluginRegistryImpl.isExtensionPointAvailable(pluginId, pointId:
    String): boolean;
var
  descriptor: IPluginDescriptor;
  it: IIntfIterator;
  point: IExtensionPoint;
begin
  Result := False;
  Supports(registeredPlugins.GetValue(pluginId),
      IPluginDescriptor, descriptor);
//  descriptor := IPluginDescriptor(registeredPlugins.GetValue(pluginId));
  if (descriptor = nil) then
    Exit;

  it := descriptor.getExtensionPoints.First;
  while it.HasNext do
  begin
    Supports(it.Next, IExtensionPoint, point);
//    point := IExtensionPoint(it.Next);
    if Rcl.Str.IsEqual(point.getId, pointId) then
    begin
      Result := point.isValid;
      Exit;
    end;
  end;

end;

function TPluginRegistryImpl.isPluginDescriptorAvailable(pluginId: string):
    Boolean;
begin
  Result := registeredPlugins.containsKey(pluginId);
end;

function TPluginRegistryImpl.makeUniqueId(pluginId, id: String): String;
begin
  Result := pluginId + UNIQUE_SEPARATOR + id;
end;

function TPluginRegistryImpl.makeUniqueId(pluginId: String; version: TVersion):
    String;
begin
  Result := pluginId + UNIQUE_SEPARATOR + version.toString;
end;

function TPluginRegistryImpl.readManifestInfo(url: string): IManifestInfo;
begin
  try
    Result := TManifestInfoImpl.Create(manifestParser.parseManifestInfo(url));
  except
    on e: Exception do
      raise EManifestProcessing.Create(PACKAGE_NAME,
                  'manifestParsingError', url, e); //$NON-NLS-1$
  end;
end;

function TPluginRegistryImpl.register(manifests : IStrList): IStrIntfMap;
var
  registeredPoints: IIntfList;
  registeredExtensions: IStrIntfMap;
  it: IIterator;
  it1, it2, it3: IIntfIterator;
  it4: IStrIterator;
  point: IExtensionPoint;
//  pointObj: TObject;
  ext: IExtension;
//  extObj: TObject;

  mapResult: IStrIntfMap;
  plugins: IStrMap;
  fragments: IStrMap;

  I: Integer;
  url: string;
  model: TModelPluginManifest;
  registryChangeData: TRegistryChangeDataImpl;
  descr: IPluginDescriptor;
//  descrObj: TObject;
//  urlObj: TObject;
  fragment: IPluginFragment;
begin
  // collecting registered extension points and extensions
  registeredPoints := TIntfLinkedList.Create; //<ExtensionPointImpl>
  registeredExtensions := TStrIntfHashMap.Create; //<extensionUid, ExtensionImpl>
  it := registeredPlugins.Values.First;
  while it.HasNext do
  begin
//    descrObj := it.Next;
    Supports(it.Next, IPluginDescriptor, descr);
    it2 := descr.getExtensionPoints.First;
    while it2.HasNext do
    begin
//      pointObj := it2.Next;
      Supports(it2.Next, IExtensionPoint, point);
//      point := it2.Next as IExtensionPoint;
      registeredPoints.Add(point);
      it3 := point.getConnectedExtensions.First;
      while it3.HasNext do
      begin
//        extObj := it3.Next;
        Supports(it3.Next, IExtension, ext);
//        ext := it3.Next as IExtension;
        registeredExtensions.PutValue(ext.getUniqueId, ext);
      end;
    end;
  end;
  mapResult := TStrIntfHashMap.Create; //<URL, PluginDescriptor or PluginFragment>
  plugins := TStrHashMap.Create(16, False); //<URL, ModelPluginDescriptor>
  fragments := TStrHashMap.Create(16, False); //<URL, ModelPluginFrafment>
  // parsing given manifests
  registrationReport.Add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR, 'manifestsParsingStart', nil)); //$NON-NLS-1$
  for I := 0 to manifests.Size - 1 do
  begin
    url := manifests.Items[I];
    try
      model := manifestParser.parseManifest(url);
    except
      on e: Exception do
      begin
        log.error('can''t parse manifest file ' + url);
        if stopOnError then
          raise EManifestProcessing.Create(PACKAGE_NAME,
                      'manifestParsingError', url, e);
        registrationReport.Add(TReportItemImpl.Create(
                SEVERITY_ERROR, nil,
                ERROR_MANIFEST_PROCESSING_FAILED,
                'manifestParsingError', [url, e.Message])); //$NON-NLS-1$
        Continue;
      end;
    end;
    if (model is TModelPluginFragment) then
    begin
        fragments.PutValue(url, model);
        Continue;
    end;
    if (not (model is TModelPluginDescriptor)) then
    begin
        log.warn('URL ' + url //$NON-NLS-1$
                + ' points to XML document of unknown type'); //$NON-NLS-1$
        continue;
    end;
    plugins.PutValue(url, model);
  end; 

  log.debug('manifest files parsed, plugins.size=' + IntToStr(plugins.size) //$NON-NLS-1$
          + ', fragments.size=' + IntToStr(fragments.size)); //$NON-NLS-1$
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR, 'manifestsParsingFinish', //$NON-NLS-1$
          [IntToStr(plugins.size), IntToStr(fragments.size)]));
  checkVersions(plugins);
  log.debug('plug-ins versions checked, plugins.size=' + IntToStr(plugins.size)); //$NON-NLS-1$
  checkVersions(fragments);
  log.debug('plug-in fragments versions checked, fragments.size=' //$NON-NLS-1$
          + IntToStr(fragments.size));

  registryChangeData := TRegistryChangeDataImpl.Create;
  // registering new plug-ins
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'registeringPluginsStart', nil)); //$NON-NLS-1$

  it4 := plugins.KeySet.First;
  while it4.HasNext do
  begin
    url := it4.Next;
    descr := registerPlugin(url, TModelPluginDescriptor(plugins.Items[url]),
      registryChangeData);
    if descr <> nil then
      mapResult.PutValue(url, descr);
  end;
  plugins.clear();
  
  // registering new plug-in fragments
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'registeringFragmentsStart', nil)); //$NON-NLS-1$
  it4 := fragments.KeySet.First;
  while it4.HasNext do
  begin
    url := it4.Next;
    fragment := registerFragment(url, TModelPluginFragment(fragments.Items[url]),
                  registryChangeData);
    if fragment <> nil then
      mapResult.PutValue(url, fragment);
  end;
  fragments.clear();
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'registeringPluginsFinish', //$NON-NLS-1$
          [IntToStr(registeredPlugins.size)]));
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'registeringFragmentsFinish', //$NON-NLS-1$
          [IntToStr(registeredFragments.size)]));
  log.info('plug-in and fragment descriptors registered - ' //$NON-NLS-1$
          + IntToStr(mapResult.Size));
  dump;
  if (mapResult.isEmpty) then
  begin
      Result := mapResult;
      Exit;
  end;

  // notify all interested members that plug-ins set has been changed
  it1 := registeredPoints.First;
  while it1.HasNext do
    if Supports(it1.Next, IExtensionPoint, point) then
      TExtensionPointImpl(point.Implementor).registryChanged;

  if ((not listeners.isEmpty) or log.isDebugEnabled) then
  begin
    // analyze changes in extensions set
    it := registeredPlugins.values.First;
    while it.HasNext do
    begin
//      it2 := IPluginDescriptor(it.Next).getExtensionPoints.First;
      Supports(it.Next, IPluginDescriptor, descr);
      it2 := descr.getExtensionPoints.First;
      while it2.HasNext do
      begin
//        it3 := IExtensionPoint(it2.Next).getConnectedExtensions.First;
        Supports(it2.Next, IExtensionPoint, point);
        it3 := point.getConnectedExtensions.First;
        while it3.HasNext do
        begin
//          ext := IExtension(it3.Next);
          Supports(it3.Next, IExtension, ext);
          if not registeredExtensions.ContainsKey(ext.getUniqueId) then
            registryChangeData.putAddedExtension(ext.getUniqueId,
                            makeUniqueId(ext.getExtendedPluginId,
                                    ext.getExtendedPointId))
          else
          begin
            registeredExtensions.remove(ext.getUniqueId);
            if (registryChangeData.modifiedPlugins.contains(
                    ext.getDeclaringPluginDescriptor.getId)
                    or registryChangeData.modifiedPlugins
                        .contains(ext.getExtendedPluginId)) then
                registryChangeData.putModifiedExtension(
                        ext.getUniqueId,
                        makeUniqueId(ext.getExtendedPluginId,
                                ext.getExtendedPointId));
          end;  
        end;
      end;
    end;

    it1 := registeredExtensions.Values.First;
    while it1.HasNext do
    begin
//      ext := IExtension(it.Next);
      Supports(it1.Next, IExtension, ext);
      registryChangeData.putRemovedExtension(ext.getUniqueId,
              makeUniqueId(ext.getExtendedPluginId,
                      ext.getExtendedPointId));
    end;
    fireEvent(registryChangeData);
  end;

  if registryChangeData <> nil then
    FreeAndNil(registryChangeData);
  

  Result := mapResult;
end;

function TPluginRegistryImpl.registerFragment(url: string; model:
    TModelPluginFragment; registryChangeData: TRegistryChangeDataImpl):
    IPluginFragment;
var
  fragment: TPluginFragmentImpl;
  isRegistered: Boolean;
  descr: TPluginDescriptorImpl;
begin
  log.debug('registering plug-in fragment descriptor, URL - ' + url); //$NON-NLS-1$
  fragment := nil;
  try
      fragment := TPluginFragmentImpl.Create(Self, model, url);
      // register fragment with all matches plug-ins
      isRegistered := false;
      descr := TPluginDescriptorImpl(getPluginDescriptor(
                  fragment.getPluginId));
      if (fragment.matches(descr)) then
      begin
          descr.registerFragment(fragment);
          if (not registryChangeData.addedPlugins.contains(
                  descr.getId)) then
              registryChangeData.modifiedPlugins.add(descr.getId);

          isRegistered := true;
      end;
      if (not isRegistered) then
      begin
          log.warn('no matching plug-ins found for fragment ' //$NON-NLS-1$
                  + fragment.getUniqueId);
//          registrationReport.add(TReportItemImpl.Create(nil,
//                  SEVERITY_WARNING, nil,
//                  ERROR_NO_ERROR,
//                  'noMatchingPluginFound', [fragment.getUniqueId])); //$NON-NLS-1$
      end;
//      registrationReport.add(TReportItemImpl.Create(nil,
//              SEVERITY_INFO, nil,
//              ERROR_NO_ERROR,
//              'fragmentRegistered', [fragment.getUniqueId])); //$NON-NLS-1$
  except
    on mpe: EManifestProcessing do
    begin
      log.error('failed registering plug-in fragment descriptor, URL - ' //$NON-NLS-1$
              + url{, mpe});
      if (stopOnError) then
          raise mpe;
      
//      registrationReport.add(TReportItemImpl.Create(nil,
//              SEVERITY_ERROR, nil,
//              ERROR_MANIFEST_PROCESSING_FAILED,
//              'fragmentRegistrationFailed', [url.Text, mpe.Message])); //$NON-NLS-1$
      Result := nil;
      Exit;
    end;
  end;
  registeredFragments.PutValue(fragment.getId, fragment);
  Result := fragment;
end;

procedure TPluginRegistryImpl.registerListener(listener:
    IRegistryChangeListener);
begin
  if listeners.contains(listener) then
    Exit;
//      raise EIllegalArgument.Create('listener '  //$NON-NLS-1$
//              + ' already registered'); //$NON-NLS-1$

  listeners.add(listener);
end;

function TPluginRegistryImpl.registerPlugin(url: string; model:
    TModelPluginDescriptor; registryChangeData: TRegistryChangeDataImpl):
    IPluginDescriptor;
var
  descResult: TPluginDescriptorImpl;
  it: IIterator;
  fragment: TPluginFragmentImpl;
begin
  log.debug('registering plug-in, URL - ' + url); //$NON-NLS-1$
  descResult := nil;
  try
      descResult := TPluginDescriptorImpl.Create(Self, model, url);
      registryChangeData.addedPlugins.add(descResult.getId);
      // applying fragments to the new plug-in
      it := registeredFragments.Values.First;
      while it.HasNext do
      begin
        fragment := TPluginFragmentImpl(it.Next);
        if fragment.matches(descResult) then
          descResult.registerFragment(fragment);
      end;

//      registrationReport.add(TReportItemImpl.Create(nil,
//              SEVERITY_INFO, nil,
//              ERROR_NO_ERROR,
//              'pluginRegistered', [descResult.getUniqueId])); //$NON-NLS-1$
  except
    on mpe: EManifestProcessing do
    begin
      log.error('failed registering plug-in, URL - ' + url{, mpe}); //$NON-NLS-1$
      if (stopOnError) then
          raise mpe;
      
//      registrationReport.add(TReportItemImpl.Create(nil,
//              SEVERITY_ERROR, nil,
//              ERROR_MANIFEST_PROCESSING_FAILED,
//              'pluginRegistrationFailed', [url.Text, mpe.Message])); //$NON-NLS-1$
      Result := nil;
      Exit;
    end;
  end;
  registeredPlugins.PutValue(descResult.getId, descResult);
  Result := descResult;
end;

function TPluginRegistryImpl.toString: string;
begin
  Result := '';
end;

function TPluginRegistryImpl.unregister(ids: TStrings): IStrCollection;
var
  registeredPoints: IIntfList;
  registeredExtensions: IStrIntfMap;
  it: IIterator;
  it1, it2, it3: IIntfIterator;
  point: IExtensionPoint;
//  pointObj: TObject;
  ext: IExtension;
//  extObj: TObject;
  setResult: IStrSet;
  registryChangeData: TRegistryChangeDataImpl;
  removingPlugins: IStrIntfMap;
  removingFragments: IStrIntfMap;
  I: Integer;
  descr, depDescr: IPluginDescriptor;
//  descrObj, depDescrObj: TObject;
  fragment: IPluginFragment;
//  fragmentObj: TObject;
begin
  // collecting registered extension points and extensions
  registeredPoints := TIntfLinkedList.Create; //<ExtensionPointImpl>
  registeredExtensions := TStrIntfHashMap.Create; //<extensionUid, ExtensionImpl>
  it := registeredPlugins.Values.First;
  while it.HasNext do
  begin
    Supports(it.Next, IPluginDescriptor, descr);
    it2 := descr.getExtensionPoints.First;
    while it2.HasNext do
    begin
//      pointObj := it2.Next;
      Supports(it2.Next, IExtensionPoint, point);
//      point := IExtensionPoint(it2.Next);
      registeredPoints.Add(point);
      it3 := point.getConnectedExtensions.First;
      while it3.HasNext do
      begin
//        extObj := it3.Next;
        Supports(it3.Next, IExtension, ext);
//        ext := IExtension(it3.Next);
        registeredExtensions.PutValue(ext.getUniqueId, ext);
      end;
    end;
  end;

  setResult := TStrHashSet.Create;
  registryChangeData := TRegistryChangeDataImpl.Create;
  // collect objects to be unregistered
  registrationReport.add(TReportItemImpl.Create(
          SEVERITY_INFO, nil,
          ERROR_NO_ERROR, 'unregisteringPrepare', nil)); //$NON-NLS-1$

  removingPlugins := TStrIntfHashMap.Create;
  removingFragments := TStrIntfHashMap.Create;

  for I := 0 to ids.Count - 1 do
  begin
//    descrObj := registeredPlugins.GetValue(ids[I]);
    Supports(registeredPlugins.GetValue(ids[I]), IPluginDescriptor, descr);
    if (descr <> nil) then
    begin
      it1 := getDependingPlugins(descr).First;
      while it1.HasNext do
      begin
//        depDescrObj := it.Next;
        Supports(it1.Next, IPluginDescriptor, depDescr);
//        depDescr := IPluginDescriptor(it.next);
        removingPlugins.PutValue(depDescr.getId, depDescr);
        registryChangeData.removedPlugins.add(depDescr.getId);
      end;
      removingPlugins.PutValue(descr.getId, descr);
      registryChangeData.removedPlugins.add(descr.getId);
      Continue;
    end;
//    fragmentObj := registeredFragments.GetValue(ids[i]);
    Supports(registeredFragments.GetValue(ids[i]), IPluginFragment, fragment);
//    fragment := IPluginFragment(registeredFragments.GetValue(ids[i]));
    if (fragment <> nil) then
    begin
      removingFragments.PutValue(fragment.getId, fragment);
      continue;
    end;
    registrationReport.add(TReportItemImpl.Create(
            SEVERITY_WARNING, nil,
            ERROR_NO_ERROR,
            'pluginToUngregisterNotFound', [ids[i]])); //$NON-NLS-1$
  end;
  // collect fragments for removing plug-ins
  it1 := removingPlugins.values.First;
  while it1.HasNext do
  begin
//    descrObj := it.next;
    Supports(it1.next, IPluginDescriptor, descr);
//    descr := IPluginDescriptor(it.next);
    it2 := descr.getFragments.First;
    while it2.HasNext do
    begin
//      fragmentObj := it2.next;
      Supports(it2.next, IPluginFragment, fragment);
//      fragment := IPluginFragment(it2.next);
      if (removingFragments.containsKey(fragment.getId)) then
          Continue;
      removingFragments.PutValue(fragment.getId, fragment);
    end;
  end;
  // notify about plug-ins removal first
  fireEvent(registryChangeData);
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'unregisteringFragmentsStart', nil)); //$NON-NLS-1$
  it1 := removingFragments.values.First;
  while it1.HasNext do
  begin
//      fragment := TPluginFragmentImpl(it.next);
      if Supports(it1.Next, IPluginFragment, fragment)then
        unregisterFragment(TPluginFragmentImpl(fragment.Implementor));
      if (not removingPlugins.containsKey(fragment.getPluginId)) then
          registryChangeData.modifiedPlugins.add(fragment.getPluginId);
      
      SetResult.add(fragment.getUniqueId);
  end;
  removingFragments.clear;
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'unregisteringPluginsStart', nil)); //$NON-NLS-1$
  it1 := removingPlugins.values.First;

  while it1.HasNext do
  begin
//    descrObj := it.next;
    Supports(it.next, IPluginDescriptor, descr);
//    descr := IPluginDescriptor(it.next);
    unregisterPlugin(TPluginDescriptorImpl(descr.Implementor));
    setResult.add(descr.getUniqueId);
  end;
  removingPlugins.clear;
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'unregisteringPluginsFinish', //$NON-NLS-1$
          [IntToStr(registeredPlugins.size)]));
  registrationReport.add(TReportItemImpl.Create(SEVERITY_INFO,
          nil, ERROR_NO_ERROR,
          'unregisteringFragmentsFinish', //$NON-NLS-1$
          [IntToStr(registeredFragments.size)]));
  log.info('plug-in and fragment descriptors unregistered - ' //$NON-NLS-1$
          + IntToStr(result.size));
  dump;
  if (result.isEmpty) then
  begin
      Result := setResult;
      Exit;
  end;
  // notify all interested members that plug-ins set has been changed
  it1 := registeredPoints.First;
  while it1.HasNext do
    if Supports(it1.Next, IExtensionPoint, point) then
      TExtensionPointImpl(point.Implementor).registryChanged;

  it1 := registeredExtensions.values.First;
  while it1.HasNext do
    if Supports(it1.Next, IExtension, ext) then
      TExtensionImpl(ext.Implementor).registryChanged;

  if ((not listeners.isEmpty) or log.isDebugEnabled) then
  begin
      // analyze changes in extensions set
      it := registeredPlugins.values.First;
      while it.HasNext do
      begin
//        descrObj := it.next;
        Supports(it.next, IPluginDescriptor, descr);
        it2 := descr.getExtensionPoints.First;
        while it2.HasNext do
        begin
//          pointObj := it2.next;
          Supports(it2.next, IExtensionPoint, point);
          it3 := point.getConnectedExtensions.First;
          while it3.HasNext do
          begin
//            extObj := it3.next;
            Supports(it3.next, IExtension, ext);
//            ext := IExtension(it3.Next);
            if not registeredExtensions.containsKey(
                          ext.getUniqueId) then
               registryChangeData.putAddedExtension(
                        ext.getUniqueId,
                        makeUniqueId(ext.getExtendedPluginId,
                                ext.getExtendedPointId))
            else
            begin
                registeredExtensions.remove(ext.getUniqueId);
                if (registryChangeData.modifiedPlugins.contains(
                        ext.getDeclaringPluginDescriptor.getId)
                        or registryChangeData.modifiedPlugins
                            .contains(ext.getExtendedPluginId)) then
                    registryChangeData.putModifiedExtension(
                            ext.getUniqueId,
                            makeUniqueId(ext.getExtendedPluginId,
                                    ext.getExtendedPointId));
            end;
          end;
        end;
      end;
      it1 := registeredExtensions.values.First;
      while it1.HasNext do
      begin
//        extObj := it1.next;
        Supports(it1.next, IExtension, ext);
//        ext := IExtension(it.Next);
        registryChangeData.putRemovedExtension(ext.getUniqueId,
                makeUniqueId(ext.getExtendedPluginId,
                        ext.getExtendedPointId));
      end;
      // fire event
      fireEvent(registryChangeData);
  end;
  Result := setResult;
end;

procedure TPluginRegistryImpl.unregisterFragment(fragment: TPluginFragmentImpl);
var
  descr: TPluginDescriptorImpl;
begin
  descr := TPluginDescriptorImpl(registeredPlugins.GetValue(fragment.getPluginId));
  if (descr <> nil) then
      descr.unregisterFragment(fragment);

  registeredFragments.remove(fragment.getId());
//  registrationReport.add(new ReportItemImpl(
//          ReportItem.SEVERITY_INFO, null,
//          ReportItem.ERROR_NO_ERROR,
//          'fragmentUnregistered', fragment.getUniqueId())); //$NON-NLS-1$
end;

procedure TPluginRegistryImpl.unregisterListener(listener:
    IRegistryChangeListener);
begin
  if (not listeners.remove(listener)) then
      raise EIllegalArgument.Create('[unregisterListener] unknown listener!'); //$NON-NLS-1$

end;

procedure TPluginRegistryImpl.unregisterPlugin(descr: TPluginDescriptorImpl);
begin
  registeredPlugins.remove(descr.getId());
//  registrationReport.add(new ReportItemImpl(
//          ReportItem.SEVERITY_INFO, null,
//          ReportItem.ERROR_NO_ERROR,
//          'pluginUnregistered', descr.getUniqueId())); //$NON-NLS-1$
end;

constructor TLibraryImpl.Create(descr: TPluginDescriptorImpl; aFragment:
    TPluginFragmentImpl; aModel: TModelLibrary);
var
  it: IStrIterator;
  exportPrefix: string;
begin
  inherited Create(descr, aFragment, aModel.id, aModel.documentation);
  model := aModel;

  if model.path = '' then
    raise EManifestProcessing.Create(PACKAGE_NAME,
          'libraryPathIsBlank'); //$NON-NLS-1$

  exportList := TStrArrayList.Create(model._exports.Size);
  it := model._exports.First;
  while it.HasNext do
  begin
    exportPrefix := it.Next;
    if exportPrefix = '' then
      raise EManifestProcessing.Create(PACKAGE_NAME,
            'exportPrefixIBlank'); //$NON-NLS-1$
    Rcl.Str.Replace(exportPrefix, '\\', '.');
    Rcl.Str.Replace(exportPrefix, '/', '.');
    if Rcl.Str.StartsWith(exportPrefix, '.') then
      exportPrefix := Rcl.Str.SubString(exportPrefix, 2);
    exportList.Add(exportPrefix);
  end;
  //exportList := Collections.unmodifiableList(exportList);
  log.debug('object instantiated: ' + Self.ClassName); //$NON-NLS-1$
end;

function TLibraryImpl.getExports: IStrCollection;
begin
  Result := exportList;
end;

function TLibraryImpl.getPath: string;
begin
  Result := model.path;
end;

function TLibraryImpl.getUniqueId: string;
begin
  Result := getDeclaringPluginDescriptor().getRegistry().makeUniqueId(
          getDeclaringPluginDescriptor().getId(), getId());
end;

function TLibraryImpl.getVersion: TVersion;
begin
  Result := model.Version;
end;

function TLibraryImpl.isCodeLibrary: Boolean;
begin
  Result := model.isCodeLibrary;
end;

function TLibraryImpl.toString: string;
begin
  Result := '{Library: uid=' + getUniqueId + '}';
end;

constructor TManifestInfoImpl.Create(aModel: TModelManifestInfo; aOwned:
    Boolean = True);
begin
  inherited Create(True);
  FOwned := aOwned;
  model := aModel;
end;

destructor TManifestInfoImpl.Destroy;
begin
  if FOwned then
    FreeAndNil(model);
  inherited;

end;

function TManifestInfoImpl.getId: string;
begin
  Result := model.Id;
end;

function TManifestInfoImpl.getMatchingRule: string;
begin
  Result := model.match;
end;

function TManifestInfoImpl.getPluginId: string;
begin
  Result := model.pluginId;
end;

function TManifestInfoImpl.getPluginVersion: TVersion;
begin
  Result := model.pluginVersion;
end;

function TManifestInfoImpl.getVendor: string;
begin
  Result := model.vendor;
end;

function TManifestInfoImpl.getVersion: TVersion;
begin
  Result := model.version;
end;


initialization
  RegisterClass(TPluginRegistryImpl);

end.
